﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using DimensionsHelper.Common.Contracts.Metadata;

namespace DimensionsHelper.Services.Metadata;

public class MetadataDocumentDesign : IMetadataDocumentDesign
{
    internal MetadataDocumentDesign(IMetadataDocument document)
    {
        Properties = new Properties(document, document);
        Fields = new Fields(document, document);
        Types = new MetadataTypes(document, document);
        Pages = new Pages(document, document);
        Routings = new Routings(document);
    }

    public IProperties Properties { get; }

    public IFields Fields { get; }

    public IMetadataTypes Types { get; }

    public IPages Pages { get; }

    public IRoutings Routings { get; }
}


public class MetadataDocument : IMetadataDocument
{
    public const string MdmXmlNamespace = @"http://www.spss.com/mr/dm/metadatamodel/Arc 3/2000-02-04";
    private readonly List<string> _atoms;
    private readonly CategoryMap _categoryMap;
    private readonly Contexts _contexts;

    private readonly DataSources _dataSources;

    private readonly MetadataDocumentDesign _design;
    private readonly Fields _fields;
    private readonly MetadataLabels _label;
    private readonly Contexts _labelTypes;
    private readonly Languages _languages;
    private readonly VariableInstances _mapping;
    private readonly Notes _notes;
    private readonly Pages _pages;
    private readonly Properties _properties;
    private readonly Contexts _routingContexts;
    private readonly Routings _routings;
    private readonly SaveLogs _saveLogs;
    private readonly Contexts _scriptContexts;
    private readonly Templates _templates;

    private readonly MetadataTypes _types;
    private readonly Variables _variables;
    private readonly VersionList _versionList;

    public MetadataDocument()
    {
        Id = Guid.NewGuid().ToString();
        DataSubVersion = 0;
        DbFilterValidation = true;
        _dataSources = new DataSources(this);
        _properties = new Properties(this);
        _templates = new Templates(this);
        _label = new MetadataLabels(this);
        _variables = new Variables(this, this);
        _pages = new Pages(this, this);
        _mapping = new VariableInstances(this);
        _languages = new Languages(this) { Parent = this };
        _contexts = new Contexts(this) { Parent = this };
        _labelTypes = new Contexts(this) { Parent = this };
        _routingContexts = new Contexts(this) { Parent = this };
        _scriptContexts = new Contexts(this) { Parent = this };
        _atoms = [];
        _categoryMap = new CategoryMap();
        _versionList = new VersionList(this);
        _saveLogs = new SaveLogs();
        _notes = new Notes(this, this);

        _types = new MetadataTypes(this);
        _fields = new Fields(this);
        _routings = new Routings(this);
        _design = new MetadataDocumentDesign(this);

        ObjectType = ObjectType.Document;
        CreateVersion = "6.0.1.1.60";
        SystemVariable = false;
        DataVersion = 0;
        LastVersion = string.Empty;
        Style = null;
        LabelStyle = null;

        XmlNamespace = MdmXmlNamespace;
        Versions = null;
        Versioned = true;
    }

    public void Close()
    {
        _dataSources.Clear();
        _properties.Clear();
        _templates.Clear();
        _languages.Clear();
        _contexts.Clear();
        _labelTypes.Clear();
        _routingContexts.Clear();
        _scriptContexts.Clear();
        _atoms.Clear();
        _categoryMap.Clear();
        _versionList.Clear();
    }

    public void Open(string fileName)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException();
        }

        var serializer = new MetadataSerializer();
        serializer.Deserialize(fileName, this);
    }
        
    public void SetProperty(string name, string value)
    {
        Properties.Get(name, Context).SetValue(value);
    }

    public void SetProperty(string name, int value)
    {
        Properties.Get(name, Context).SetValue(value);
    }

    public void SetProperty(string name, double value)
    {
        Properties.Get(name, Context).SetValue(value);
    }

    public void SetProperty(string name, bool value)
    {
        Properties.Get(name, Context).SetValue(value);
    }

    public void SetProperty(string name, IProperty property)
    {
        Properties.Get(name, Context).SetValue(property);
    }

    public void SetProperty(string name, IProperties documentProperties)
    {
        Properties.Get(name, Context).SetValue(documentProperties);
    }

    #region IMetadataDocument Properties
        
    public IVersionObjectCollection? Versions { get; internal set; }

    public IStyles? Style { get; set; }

    public IStyles? LabelStyle { get; set; }

    public IMetadataObject? Parent => null;

    public string Id { get; internal set; }

    public string Name => "";

    public string FullName => "";

    public int DataSubVersion { get; internal set; }

    public bool DbFilterValidation { get; internal set; }

    public IDataSources DataSources => _dataSources;

    public ITemplates Templates => _templates;

    public IMetadataLabels Label => _label;

    public IVariables Variables => _variables;

    public IPages Pages => _pages;

    public IVariableInstances Mapping => _mapping;

    public ILanguages Languages => _languages;

    public IContexts Contexts => _contexts;

    public IContexts LabelTypes => _labelTypes;

    public IContexts RoutingContexts => _routingContexts;

    public IContexts ScriptContexts => _scriptContexts;

    public IList<string> Atoms => _atoms;

    public ICategoryMap CategoryMap => _categoryMap;

    public ISaveLogs SaveLogs => _saveLogs;

    public INotes Notes => _notes;

    public IMetadataTypes Types => _types;

    public IFields Fields => _fields;

    public IRoutings Routings => _routings;

    public IMetadataDocumentDesign Design => _design;

    public string XmlNamespace { get; }

    public string CreateVersion { get; internal set; }

    public string LastVersion { get; internal set; }

    public bool SystemVariable { get; internal set; }

    public int DataVersion { get; internal set; }

    public IMetadataDocument Document => this;

    public ObjectType ObjectType { get; }

    public IProperties Properties => _properties;

    public IVersionList VersionList => _versionList;

    int? IMetadataDocumentInfo.DataSubVersion => DataSubVersion;

    public bool Versioned { get; set; }

    public string Context
    {
        get => Contexts.Base.Name;

        set => ((Contexts)Contexts).BaseName = value;
    }

    public string Language
    {
        get => Languages.Base.Name;

        set => ((Languages)Languages).BaseName = value;
    }

    public string LabelType
    {
        get => LabelTypes.Base.Name;

        set => ((Contexts)LabelTypes).BaseName = value;
    }

    #endregion


    #region Create Objects Methods

    // 添加新的变量时，需要同时更新多个集合，因此，使用的集合对象都没有外部更新方法。
    // 1. 如果添加普通变量，需要在 Variables 添加变量定义，然后在 Design.Fields 和 Fields 中添加变量引用。
    // 2. 如果添加多层变量，在 Design.Fields和 Fields 中添加对应类型变量。
    // 3. 如果在多层变量中添加新的次级变量，同样要进行检查。对应类型的添加方法应当直接调用此处的方法。

    public IVariable CreateVariable(string name, FieldDataType dataType)
    {
        var variable = new Variable(this, name, dataType);
        _variables.Add(variable);
        return variable;
    }

    public IVariable CreateField(string name, FieldDataType dataType)
    {
        var variable = CreateVariable(name, dataType);
        var reference = new Variable(this, name, variable.Id)
        {
            Id = "_" + variable.Id
        };
        ((Fields)Design.Fields).Add(reference);
        _fields.Add(reference);
        return variable;
    }

    public IArray CreateArray(string name, IteratorTypes iteratorType)
    {
        var arr = new MetadataArray(this, name)
        {
            IteratorType = iteratorType,
            IsGrid = true
        };

        ((Fields)_design.Fields).Add(arr);
        _fields.Add(arr);

        return arr;
    }

    public ICompound CreateCompound(string name)
    {
        var compound = new Compound(this, name);
        ((Fields)_design.Fields).Add(compound);
        _fields.Add(compound);
        return compound;
    }

    public IBlock CreateBlock(string name)
    {
        var block = new Block(this, name);
        ((Fields)_design.Fields).Add(block);
        _fields.Add(block);
        return block;
    }

    public IPage CreatePage(string name)
    {
        var page = Pages.CreateObject(name);
        _pages.Add(page);
        return page;
    }

    public IElementList CreateElementList(string name)
    {
        var list = _types.CreateObject(name);
        ((MetadataTypes)_design.Types).Add(list);
        _types.Add(list);
        return list;
    }
        
    public T? FindParent<T>() where T : IMetadataObject
    {
        return default;
    }

    #endregion
}


public class CategoryId(string name, int value) : ICategoryId
{
    public string Name { get; } = name;
    public int Value { get; } = value;
}


public class CategoryMap : ICategoryMap
{
    private readonly List<ICategoryId> _elements = [];
    private readonly Dictionary<string, int> _nameToValue = new();
    private readonly Dictionary<int, string> _valueToName = new();

    public int? GetIdByName(string name)
    {
        return _nameToValue.TryGetValue(name.ToLower(), out var id) ? id : null;
    }
        
        
    public string? GetNameById(int value)
    {
        return _valueToName.TryGetValue(value, out var res) ? res : null;
    }

        
    public int Allocate(string name)
    {
        if (_nameToValue.TryGetValue(name.ToLower(), out var val))
        {
            return val;
        }

        var value = 1;

        if (_elements.Count > 0)
        {
            value = _elements[^1].Value + 1;
        }

        TryAdd(name, value);

        return value;
    }

    public void Clear()
    {
        _elements.Clear();
        _nameToValue.Clear();
        _valueToName.Clear();
    }

    public IEnumerator<ICategoryId> GetEnumerator()
    {
        return _elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    internal bool TryAdd(string name, int value)
    {
        var lowerName = name.ToLower();

        if (_nameToValue.ContainsKey(lowerName) || _valueToName.ContainsKey(value))
        {
            return false;
        }

        var lastValue = _elements.Count == 0 ? 0 : _elements[^1].Value;

        _nameToValue.Add(lowerName, value);
        _valueToName.Add(value, lowerName);
        _elements.Add(new CategoryId(name, value));

        if (value < lastValue)
        {
            _elements.Sort((a, b) => a.Value - b.Value);
        }

        return true;
    }
}


/// <summary>
///     MDM文档中使用scripttype tag定义的对象。
/// </summary>
public class MetadataScriptType : IMetadataScriptType
{
    private readonly List<MetadataScript> _deletedScripts = [];

    private readonly List<MetadataScript> _scripts = [];

    /// <summary>
    ///     MDM文档中使用scripttype tag定义的对象。
    /// </summary>
    public MetadataScriptType(IMetadataDocument document, string type, string contextType)
    {
        Document = document;
        Type = type;
        Context = contextType;
    }

    public IEnumerable<MetadataScript> DeletedScripts => _deletedScripts;

    public IMetadataDocument Document { get; }

    public string Type { get; }

    public string Context { get; }

    public InterviewModes InterviewModes { get; set; } = InterviewModes.Web;

    public bool UseKeyCode { get; set; }
        
    public IVersionObjectCollection? Versions { get; set; }

    public MetadataScript AddNew(string name)
    {
        var script = new MetadataScript(name);
        _scripts.Add(script);
        return script;
    }

    public void Add(MetadataScript script)
    {
        _scripts.Add(script);
    }

    public MetadataScript AddDeletedScript(string name)
    {
        var script = new MetadataScript(name);
        _deletedScripts.Add(script);
        return script;
    }

    public void AddDeletedScript(MetadataScript script)
    {
        _deletedScripts.Add(script);
    }

    public IEnumerator<MetadataScript> GetEnumerator()
    {
        return _scripts.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


public class MetadataScripts(IMetadataDocument document) : IMetadataScripts
{
    private readonly List<IMetadataScriptType> _deletedScripts = [];
    private readonly List<IMetadataScriptType> _scripts = [];

    public IMetadataDocument Document { get; } = document;

    public IEnumerable<IMetadataScriptType> DeletedScripts => _deletedScripts;

    public IVersionObjectCollection? Versions { get; set; }

    public IMetadataScriptType AddNew(string type, string context)
    {
        var script = new MetadataScriptType(Document, type, context);
        _scripts.Add(script);
        return script;
    }

    public void Add(IMetadataScriptType scriptType)
    {
        _scripts.Add(scriptType);
    }

    public IMetadataScriptType AddDeletedScriptType(string type, string context)
    {
        var script = new MetadataScriptType(Document, type, context);
        _deletedScripts.Add(script);
        return script;
    }

    public void AddDeletedScriptType(IMetadataScriptType scriptType)
    {
        _deletedScripts.Add(scriptType);
    }

    #region IEnumerable

    public IEnumerator<IMetadataScriptType> GetEnumerator()
    {
        return _scripts.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _scripts.GetEnumerator();
    }

    #endregion
}


public class Goto : MetadataNamedObject, IGoto
{
    internal Goto(IGotoList list, string name, int reference)
        : base(list.Document, ObjectType.Goto, name)
    {
        List = list;
        Reference = reference;
    }

    public IGotoList List { get; }

    public int Reference { get; internal set; }
}


public class GotoList : IGotoList
{
    private readonly Dictionary<string, IGoto> _nameMap;

    internal GotoList(IMetadataDocument document, IRoutingItem routingItem)
    {
        Document = document;
        _nameMap = new Dictionary<string, IGoto>();
        RoutingItem = routingItem;
    }


    public IMetadataDocument Document { get; }

    public int Count => _nameMap.Count;

    public IRoutingItem RoutingItem { get; }

    public IGoto AddNew(string name, int reference)
    {
        var obj = new Goto(this, name, reference);
        _nameMap.Add(name.ToLower(), obj);
        return obj;
    }

    public IGoto CreateObject(string name)
    {
        return new Goto(this, name, 0);
    }

    public IGoto? Get(string name)
    {
        return _nameMap.GetValueOrDefault(name.ToLower());
    }

    public bool TryGet(string name, [MaybeNullWhen(false)] out IGoto obj)
    {
        return _nameMap.TryGetValue(name, out obj);
    }

    #region IEnumerable

    public IEnumerator<IGoto> GetEnumerator()
    {
        return _nameMap.Values.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}


public class RoutingItem : MetadataNamedObject, IRoutingItem
{
    internal RoutingItem(IMetadataDocument document, string name)
        : base(document, ObjectType.RoutingItem, name)
    {
        ItemVariableId = name;
        Goto = new GotoList(document, this);
    }

    public IVariable ItemVariable
    {
        get
        {
            if (Document.Variables.TryGetByKey(ItemVariableId, out var variable))
            {
                return variable;
            }

            throw new Exception($"未找到ID是'{ItemVariableId}'的变量定义。");
        }
    }


    public string ItemVariableId { get; set; }

    public IGotoList Goto { get; }
}


public class RoutingItems : MetadataStringKeyCollection<IRoutingItem>, IRoutingItems
{

    public T? FindParent<T>() where T : IMetadataObject
    {
        return MetadataObject.FindParent<T>(this);
    }

    public IMetadataObject Parent { get; }

    public IVersionObjectCollection? Versions { get; internal set; }
        
    internal RoutingItems(IMetadataDocument document, IMetadataObject parent)
    {
        ObjectType = ObjectType.RoutingItems;
        Document = document;
        Parent = parent;
        Properties = new Properties(document, this);
        Versions = null;
        Versioned = true;
    }

    public IProperties Properties { get; }

    public IMetadataDocument Document { get; }

    public ObjectType ObjectType { get; }

    public bool Versioned { get; set; }

    public override IRoutingItem CreateObject(string name)
    {
        return new RoutingItem(Document, name);
    }

    protected override string KeySelector(IRoutingItem item)
    {
        return item.Name;
    }
}


public class Routing : MetadataObject, IRouting
{
    internal Routing(IMetadataDocument document) : base(document, ObjectType.Routing)
    {
        Parent = Document;
        Context = string.Empty;
        InterviewModes = InterviewModes.Local;
        UseKeyCodes = false;
        Items = new RoutingItems(document, this);
    }

    public string Context { get; internal set; }

    public InterviewModes InterviewModes { get; internal set; }

    public bool UseKeyCodes { get; internal set; }

    public IRoutingItems Items { get; }
}


public class Routings : IRoutings
{
    private readonly List<Routing> _routings;

    internal Routings(IMetadataDocument document)
    {
        Document = document;
        Properties = new Properties(document);
        Scripts = new MetadataScripts(document);
        _routings = [];
        Name = string.Empty;
    }
    
    public IMetadataDocument Document { get; }

    public IProperties Properties { get; }

    public string Name { get; internal set; }
        
    public IVersionObjectCollection? Versions { get; internal set; }

    public IMetadataScripts Scripts { get; }

    public IRouting AddNew()
    {
        var item = new Routing(Document);
        _routings.Add(item);
        return item;
    }

    #region IEnumerable

    public IEnumerator<IRouting> GetEnumerator()
    {
        return _routings.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}